// The MIT License

// Copyright (c) 2009 Massachusetts Institute of Technology

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Connectable::*;
import GetPut::*;
import ClientServer::*;
import RegFile::*;
import FIFO::*;
import FIFOF::*;
import RWire::*;
import BRAM::*;

// Local includes
`include "asim/provides/low_level_platform_interface.bsh"
`include "asim/provides/soft_connections.bsh"
`include "asim/provides/processor_library.bsh"
`include "asim/provides/fpga_components.bsh"
`include "asim/provides/common_services.bsh"
`include "asim/provides/mem_services.bsh"

`include "awb/provides/soft_connections.bsh"
`include "awb/provides/soft_services.bsh"
`include "awb/provides/soft_services_lib.bsh"
`include "awb/provides/soft_services_deps.bsh"

`include "asim/provides/scratchpad_memory.bsh"

`include "asim/dict/VDEV_SCRATCH.bsh"

interface ICache#( type req_t, type resp_t );

  // Interface from processor to cache
  interface Server#(req_t,resp_t) proc_server;

  // Interface from cache to main memory
  interface Client#(MainMemReq,MainMemResp) mmem_client;

  // Interface for enabling/disabling statistics
//  interface Put#(Bool) statsEn_put;

endinterface

//----------------------------------------------------------------------
// Cache Types
//----------------------------------------------------------------------

typedef 12 CacheLineIndexSz;
typedef 20 CacheLineTagSz;
typedef 32 CacheLineSz;

typedef 20 ScratchpadIndexSz;

typedef Bit#(ScratchpadIndexSz) CacheLineIndexScratchpad;

typedef Bit#(CacheLineIndexSz) CacheLineIndex;
typedef Bit#(CacheLineTagSz)   CacheLineTag;
typedef Bit#(CacheLineSz)      CacheLine;

typedef enum 
{ 
  Init,
	FopenResp,
	EXEC_entry,
	EXEC_offset,
	EXEC_size,
	FreadResp,
  Access, 
  Evict, 
  RefillReq, 
  RefillResp 
} 
CacheStage 
deriving (Eq,Bits);

//----------------------------------------------------------------------
// Helper functions
//----------------------------------------------------------------------

function Bit#(AddrSz) getAddr( InstReq req );

  Bit#(AddrSz) addr = ?;
  case ( req ) matches
    tagged LoadReq  .ld : addr = ld.addr;
    tagged StoreReq .st : addr = st.addr;
  endcase

  return addr;

endfunction

function CacheLineIndex getCacheLineIndex( InstReq req );
  Bit#(AddrSz) addr = getAddr(req);
  Bit#(CacheLineIndexSz) index = truncate( addr >> 2 );
  return index;
endfunction

function CacheLineTag getCacheLineTag( InstReq req );
  Bit#(AddrSz)         addr = getAddr(req);
  Bit#(CacheLineTagSz) tag  = truncate( addr >> fromInteger(valueOf(CacheLineIndexSz)) >> 2 );
  return tag;
endfunction

function Bit#(AddrSz) getCacheLineAddr( InstReq req );
  Bit#(AddrSz) addr = getAddr(req);
  return ((addr >> 2) << 2);
endfunction

//----------------------------------------------------------------------
// Main module
//----------------------------------------------------------------------

module [CONNECTED_MODULE] mkInstCache( ICache#(InstReq,InstResp) );

  //-----------------------------------------------------------
  // State
	
	STDIO#(Bit#(32)) stdio_exec <- mkStdIO();

  Reg#(CacheStage) stage <- mkReg(Init);

  LUTRAM#(CacheLineIndex,Maybe#(CacheLineTag)) cacheTagRam  <- mkLUTRAMU_RegFile();
  LUTRAM#(CacheLineIndex,CacheLine)            cacheDataRam <- mkLUTRAMU_RegFile();
  
//	BRAM#(CacheLineIndex,Maybe#(CacheLineTag)) cacheTagBRam  <- mkBRAM();
//	BRAM#(CacheLineIndex,CacheLine)            cacheDataBRam <- mkBRAM();

//	MEMORY_IFC#(CacheLineIndexScratchpad, Maybe#(CacheLineTag)) cacheTagScratchpad <- mkScratchpad(`VDEV_SCRATCH_ICACHE_TAG, SCRATCHPAD_CACHED);
	MEMORY_IFC#(CacheLineIndexScratchpad, CacheLine) cacheDataScratchpad <- mkScratchpad(`VDEV_SCRATCH_ICACHE_DATA, SCRATCHPAD_CACHED);

  FIFO#(InstReq)   reqQ  <- mkFIFO();
  FIFOF#(InstResp) respQ <- mkBFIFOF1();

  FIFO#(MainMemReq)  mainMemReqQ  <- mkBFIFO1();
  FIFO#(MainMemResp) mainMemRespQ <- mkFIFO();

  Reg#(CacheLineIndex) initCounter <- mkReg(1);

  // Statistics state

//  Reg#(Bool)     statsEn        <- mkReg(False);

//  STAT num_accesses <- mkStatCounter(`STATS_INST_CACHE_NUM_ACCESSES);
//  STAT num_misses <- mkStatCounter(`STATS_INST_CACHE_NUM_MISSES);
//  STAT num_evictions <- mkStatCounter(`STATS_INST_CACHE_NUM_EVICTIONS);
//	STAT num_accesses <- mkStatCounter(statName("INST_CACHE_NUM_ACCESSES", "INST_CACHE: Number Of Accesses: "));
//	STAT num_misses <- mkStatCounter(statName("INST_CACHE_NUM_MISSES", "INST_CACHE: Number Of Misses: "));
//	STAT num_evictions <- mkStatCounter(statName("INST_CACHE_NUM_EVICTIONS", "INST_CACHE: Number Of Evictions: "));

  //-----------------------------------------------------------
  // Name some wires

  let req              = reqQ.first();
  let reqIndex         = getCacheLineIndex(req);
  let reqTag           = getCacheLineTag(req);
  let reqCacheLineAddr = getCacheLineAddr(req);
  let refill           = mainMemRespQ.first();

	let fexec <- getGlobalStringUID("exec.bin");
	let fmoderb <- getGlobalStringUID("rb");
  //-----------------------------------------------------------
  // Initialize

  rule init ( stage == Init );
//    traceTiny("mkInstCacheBlocking", "stage","i");
//    initCounter <= initCounter + 1;
//    cacheTagRam.upd(initCounter,Invalid);
//		cacheTagBRam.write(initCounter, Invalid);
		
		stdio_exec.fopen_req(fexec, fmoderb);
		stage <= FopenResp;

//    if ( initCounter == 0 )
//      stage <= Access;
  endrule
	
	Reg#(STDIO_FILE) fHandle_exec <- mkRegU();
	rule freadReq ( stage == FopenResp );
		let fxHandle <- stdio_exec.fopen_rsp();
		fHandle_exec <= fxHandle;
		stdio_exec.fread_req(fxHandle, 3);
		stage <= EXEC_entry;
	endrule

	Reg#(Bit#(32)) exec_entry <- mkReg(0);
	Reg#(Bit#(32)) exec_offset <- mkReg(0);
	Reg#(Bit#(32)) exec_size <- mkReg(0);
	rule execReadEntry(stage == EXEC_entry);
		let rsp <- stdio_exec.fread_rsp();

		if (rsp matches tagged Valid .v)
			exec_entry <= v;

		stage <= EXEC_offset;
	endrule
	rule execReadOffset(stage == EXEC_offset );
		let rsp <- stdio_exec.fread_rsp();
		if (rsp matches tagged Valid .v)
			exec_offset <= v;
		stage <= EXEC_size;
	endrule
	Reg#(Bit#(30)) flying <- mkReg(0);
	Reg#(Bit#(30)) totalRead <- mkReg(0);
	rule execReadSize(stage == EXEC_size);
		let rsp <- stdio_exec.fread_rsp();
		if (rsp matches tagged Valid .v)
		begin
			exec_size <= v;


			//let rcount = 128;
			let rcount = v>>2;
			$display("InstCache readin %d", rcount);
			stdio_exec.fread_req(fHandle_exec, 128);
			flying <= 128;
			//stdio_exec.fread_req(fHandle_exec, truncate(rcount));
			//flying <= truncate( rcount );
			stage <= FreadResp;
		end
	endrule

	rule freadExecReq(stage == FreadResp && flying == 0 );
		stdio_exec.fread_req(fHandle_exec, 32);
		flying <= 32;
	endrule

	rule freadRespsink(stage != FreadResp);
		let rsp <- stdio_exec.fread_rsp();
	endrule

	rule freadResp(stage == FreadResp && flying > 0);
		let rsp <- stdio_exec.fread_rsp();

		if (rsp matches tagged Valid .v)
		begin
			let rcount = exec_size>>2;
			cacheDataScratchpad.write(truncate(rcount-zeroExtend(flying)), v);
			$display("read %d -> %x!", flying, v);
/*
			if ( flying == 1 ) begin
				$display("eof reached!");
				stage <= Access;
			end
*/
		end
		else
			stage <= Access;

		flying <= flying - 1;
		totalRead <= totalRead + 1;
	endrule

  //-----------------------------------------------------------
  // Cache access rule

	rule reqAccess( stage == Access );
	endrule

	rule memResp(stage == Access);
		$display("memresp");
		let memResp <- cacheDataScratchpad.readRsp(); 
		respQ.enq( LoadResp{tag: 0, data:memResp});
	endrule

  rule access ( (stage == Access) && respQ.notFull() );
		reqQ.deq();
       case ( req ) matches
          
         tagged LoadReq .ld :
				 	begin
						let idx = (ld.addr - exec_offset)>>2;
//						if ( idx > 128 ) begin
//							respQ.enq(LoadResp{tag:0, data:0});
//							$display("memreq returning 0");
//						end
//						else begin
					 		cacheDataScratchpad.readReq(truncate(idx));
							$display("memreq %x %x", idx, ld.addr);
//				 	respQ.enq( LoadResp { tag  : ld.tag, data : cacheDataRam.sub(reqIndex) } );
//						end
					end

         tagged StoreReq .st :
          $display( " RTL-ERROR : %m : Stores are not allowed on the inst port!" );
       
       endcase
/*
    // Statistics

//    if ( statsEn )
//      num_accesses.incr();
  
    // Check tag and valid bit to see if this is a hit or a miss

    Maybe#(CacheLineTag) cacheLineTag = cacheTagRam.sub(reqIndex);
  
    // Handle cache hits ...

    if ( isValid(cacheLineTag) && ( unJust(cacheLineTag) == reqTag ) )
     begin
       traceTiny("mkInstCacheBlocking", "hitMiss","h");
       reqQ.deq();

       case ( req ) matches
          
         tagged LoadReq .ld :
				 	respQ.enq( LoadResp { tag  : ld.tag, data : cacheDataRam.sub(reqIndex) } );

         tagged StoreReq .st :
          $display( " RTL-ERROR : %m : Stores are not allowed on the inst port!" );
       
       endcase

     end

    // Handle cache misses - since lines in instruction cache are
    // never dirty we can always immediately issue a refill request
    
    else 
     begin
       traceTiny("mkInstCacheBlocking", "hitMiss","m");
//       if ( statsEn )
//         num_misses.incr();
//       if ( statsEn )
//         if ( isJust(cacheLineTag) )
//				   num_evictions.incr();	

       MainMemReq rfReq
        = LoadReq { tag  : 0,
	            addr : reqCacheLineAddr, size:4 };

       mainMemReqQ.enq(rfReq);
       stage <= RefillResp;    
     end
		 */

  endrule

  //-----------------------------------------------------------
  // Refill response rule
  
   rule refillResp ( stage == RefillResp );
    traceTiny("mkInstCacheBlocking", "stage","R");
    traceTiny("mkInstCacheBlocking", "refill",refill);

    // Write the new data into the cache and update the tag

    mainMemRespQ.deq();

		case ( refill ) matches
			tagged LoadResp .ld: begin

				Bit#(AddrSz)         addr = getAddr(req) + zeroExtend(ld.tag<<2);
				Bit#(CacheLineIndexSz) index = truncate( addr >> 2 );
				Bit#(CacheLineTagSz) tag  = truncate( addr >> fromInteger(valueOf(CacheLineIndexSz)) >> 2 );

				cacheTagRam.upd(index,Valid(tag));
				cacheDataRam.upd(index,ld.data);	

//				cacheTagBRam.write(index,Valid(tag));
//				cacheDataBRam.write(index,ld.data);	
				if ( ld.tag == 7 )
			    stage <= Access;
			end
			tagged StoreResp .st: stage <= Access;
		endcase
//	 	cacheTagRam.upd(reqIndex,Valid(reqTag));
//	 	cacheDataRam.upd(reqIndex,ld.data);	

  endrule

  //-----------------------------------------------------------
  // Methods

  interface Client mmem_client;
    interface Get request  = fifoToGet(mainMemReqQ);
    interface Put response = fifoToPut(mainMemRespQ);
  endinterface

  interface Server proc_server;
    //interface Put request  = tracePut("mkInstCacheBlocking", "reqTiny",fifoToPut(reqQ));
    //interface Get response = traceGet("mkInstCacheBlocking", "respTiny",fifofToGet(respQ));
    interface Put request  = fifoToPut(reqQ);
    interface Get response = fifofToGet(respQ);
  endinterface

//  interface Put statsEn_put = regToPut(statsEn);

endmodule

